<?php

/**
 * @desc Utility for account authorization. 
 *
 */
abstract class Security_Utils_Authorize 
{

    /**
     * Пытается авторизовать пользователя с указанными параметрами для исполнения
     * указанной роли.
     *
     * @param   string  $login
     * @param   string  $password
     * @param   integer $role
     * @throws  Security_Exception_Authorize
     */
    public function authorize($login, $password) 
    {
        // пытаемся определить идентификатор аккаунта
        $account_id = $this->_authorize($login, $password);

        // запоминаем полученные данные в сессии
        Security_Session::register($account_id);

        // регистрируем успешный вход в систему
        Security_Utils_BruteForce::register($account_id, Security_Utils_BruteForce::A_SUCCESS);

        // сбрасываем ошибки авторизации
        Security_Utils_BruteForce::clear($account_id, Security_Utils_BruteForce::A_AUTHORIZE);
    }

    /**
     * Проверяет параметры профайла
     *
     * @param   Security_Account   $account
     * @param   string  $password
     */
    protected function _validate($account, $password) 
    {
        if (!Security_Utils_Subject::compare($password, $account->password())) {
            $this->_bruteForce($account);
        }
    }
    
    /**
     * Try is faild. Also use this method to pass through all the authorization 
     * engine. 
     *
     * @param   Security_Account   $account
     * @param   string  $message [, mixed $arg1, …]
     * @throws  Security_Exception_Authorize
     */
    protected function _fail($account, $message) 
    {
        $args = func_get_args();
        // backtrace before throw message
        call_user_func_array(array($this, '_bruteForce'), $args);
    }

    /**
     * Возвращает данные аккаунта, необходимые для авторизации пользователя.
     *
     * @param   string  $login
     * @return  array
     */
    abstract protected function _getAccountParams($login);
    
    /**
     * @desc Backtrace brute force tries. If it is passed more than one arguments
     * then standart message is replaced with incoming. 
     *
     * @param   Security_Account   $account
     */
    private function _bruteForce($account) 
    {
        // регистрируем попытку подбора, если были введены правильный логин
        // и неправильный пароль
        Security_Utils_BruteForce::register($account->id(), SECURITY_AUTHORIZE);

        if (false !== ($tries = Security_Utils_BruteForce::warning($account->id(), SECURITY_AUTHORIZE))) {
            if ($tries > 0) {
                $this->_error('AUTHORIZE_WARNING', $tries);
            } else {
                $this->_error('AUTHORIZE_BANNED', Security_Utils_BruteForce::BAN_PERIOD);
            }
        } else {
            if (count($args = array_slice(func_get_args(), 1))) {
                call_user_func_array(array($this, '_error'), $args);
            } else {
                $this->_error('AUTHORIZE_FAILED');
            }
        }
    }

    /**
     * Регистрирует ошибку авторизации. 
     * :KLUDGE: dV 20080929: Method uses protected access modifier for backward 
     * compatibility. Change to private. 
     *
     * @param   string  $message [, mixed $arg1, …]
     * @throws  Security_Exception_Authorize
     */
    protected function _error($message) 
    {
        $args = func_get_args();
        throw new Security_Exception_Authorize(call_user_func_array('__', $args));
    }

    /**
     * Возвращает объект учетной записи с указанными параметрами.
     *
     * @param   array   $params
     * @return  Security_Account
     */
    private function _account($params) 
    {
        return new Security_Account(array_shift($params), array_shift($params), $params);
    }

    /**
     * Возвращает идентификатор субъекта безопасности, соответствующего
     * указанными логину и паролю.
     *
     * @param   string  $email
     * @param   string  $password
     * @return  integer
     * @throws  Security_Exception_Authorize
     */
    private function _authorize($login, $password) 
    {
        // поднимаем данные субъекта безопасности
        if (is_array($params = $this->_getAccountParams($login))) {

            // создаем объект учетной записи
            $account = $this->_account($params);

            if (false !== ($remain = Security_Utils_BruteForce::banned($account->id(), SECURITY_AUTHORIZE))) {
                $this->_error('AUTHORIZE_BANNED', $remain);
            }

            $this->_validate($account, $password);

            // если не возникло ошибок — пускаем пользователя
            return $account->id();
        }

        $this->_error('AUTHORIZE_FAILED');
    }
    
}

/**
 * Инкапсулирует данные учетной записи, пользователя которой нужно авторизовать.
 *
 */
class Security_Account 
{

    /**
     * Идентификатор учетной записи.
     *
     * @var integer
     */
    private $id;

    /**
     * Пароль учетной записи в зашифрованном виде.
     *
     * @var string
     */
    private $password;

    /**
     * Дополнительные параметры, специфичные для конкретной реализации модуля
     * безопасности.
     *
     * @var array
     */
    private $params;

    /**
     * Конструктор.
     *
     * @param   integer $id
     * @param   string  $password
     * @param   array   $params
     */
    public function __construct($id, $password, $params) 
    {
        $this->id = (int)$id;
        $this->password = $password;
        $this->params = $params;
    }

    /**
     * Возвращает идентификатор учетной записи.
     *
     * @return  integer
     */
    public function id() 
    {
        return $this->id;
    }

    /**
     * Возвращает пароль учетной записи в зашифрованном виде.
     *
     * @return  string
     */
    public function password() 
    {
        return $this->password;
    }

    /**
     * Возвращает значение указанного параметра.
     *
     * @param   string  $name
     * @return  mixed
     */
    public function param($name) 
    {
        return get_array_element($this->params, $name);
    }
    
}
